package com.xbongbong.pro.service.aliyun;

import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.CopyObjectResult;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PolicyConditions;
import com.aliyun.oss.model.PutObjectResult;
import com.aliyun.sts20150401.Client;
import com.aliyun.sts20150401.models.AssumeRoleRequest;
import com.aliyun.sts20150401.models.AssumeRoleResponse;
import com.aliyun.sts20150401.models.AssumeRoleResponseBody;
import com.aliyun.teaopenapi.models.Config;
import com.xbongbong.paas.constant.PaasConstant;
import com.xbongbong.paas.enums.PlatFormEnum;
import com.xbongbong.paas.toolbox.exception.XbbException;
import com.xbongbong.paas.toolbox.util.StringUtil;
import com.xbongbong.paas.toolbox.wrap.BaseDTO;
import com.xbongbong.pro.config.ProBaseConfig;
import com.xbongbong.pro.enums.errorcodes.SystemErrorCodeEnum;
import com.xbongbong.saas.enums.CompanyConfigEnum;
import com.xbongbong.sys.domain.entity.CompanyConfigEntity;
import com.xbongbong.sys.model.CompanyConfigModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Date;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 阿里云OSS服务封装
 * @author kaka
 * @time 2018-8-28
 */
@Component
public class AliyunOssService {
	private static final Logger LOG = LoggerFactory.getLogger(AliyunOssService.class);

	@Resource
	private ProBaseConfig proBaseConfig;
	@Resource
	private CompanyConfigModel companyConfigModel;

	/**
	 * 图片类型常量，可以手动添加
	 */
	private static String[] imageType = {"jpg", "jpeg", "png", "gif"};
	/**
	 * 签名有效时间，从10分钟改为1小时
	 */
	private static final Integer SIGN_EXPIRE_TIME = 60 * 60;

	public String upload(String key, byte[] imageByte) {
		InputStream content = null;
		try {
			long size = imageByte.length;
			content =  new ByteArrayInputStream(imageByte);
			putObject(proBaseConfig.getBucketName(), key,content, size);
		} catch (IOException e) {
			LOG.error("AliyunOssService.upload IOException", e);
			return "";
		} finally {
			if (content != null) {
				try {
					content.close();
				} catch (IOException e) {
					LOG.error("AliyunOssService.upload", e);
				}
			}
		}

		return proBaseConfig.getViewurl() + key;
	}

	public void putObject(String bucketName, String key,
								 InputStream content, long size) throws FileNotFoundException {
		// 初始化OSSClient
		OSSClient client = new OSSClient(proBaseConfig.getEndpoint(), proBaseConfig.getOssAccessKeyId(), proBaseConfig.getAccessKeySecret());

		try {
			// 创建上传Object的Metadata
			ObjectMetadata meta = new ObjectMetadata();

			// 必须设置ContentLength
			meta.setContentLength(size);
			//文件类型
			int lastDotIndex = key.lastIndexOf(".");
			String fileType = key.substring(lastDotIndex+1);
			if(Arrays.asList(imageType).contains(fileType)) {
				//图片类型特殊处理
				meta.setContentType("image/" + fileType);
			}

			// 上传Object.
			PutObjectResult result = client.putObject(bucketName, key, content,
					meta);
		} catch (Exception e) {
			LOG.error("putObject error", e);
		} finally {
			client.shutdown();
		}

	}

	public void putObject(String bucketName, String key, String filePath)
			throws FileNotFoundException {

		// 初始化OSSClient
		OSSClient client = new OSSClient(proBaseConfig.getEndpoint(), proBaseConfig.getOssAccessKeyId(), proBaseConfig.getAccessKeySecret());

		try {
			// 获取指定文件的输入流
			File file = new File(filePath);
			InputStream content = new FileInputStream(file);

			// 创建上传Object的Metadata
			ObjectMetadata meta = new ObjectMetadata();

			// 必须设置ContentLength
			meta.setContentLength(file.length());
			//文件类型
			int lastDotIndex = key.lastIndexOf(".");
			String fileType = key.substring(lastDotIndex+1);
			if(Arrays.asList(imageType).contains(fileType)) {
				//图片类型特殊处理
				meta.setContentType("image/" + fileType);
			}

			// 上传Object.
			PutObjectResult result = client.putObject(bucketName, key, content,
					meta);
		} catch (Exception e) {
			LOG.error("putObject error", e);
		} finally {
			client.shutdown();
		}
	}

	/**
	 * 获取aliyun oss签名，给前端用于文件上传
	 *
	 * @return 返回用于前端图片上传的签名及其他信息，在一个hashMap内
	 * @author kaka
	 * @date 2018-5-3
	 * @since v3.21
	 * @version v3.21
	 */
	public Map<String, Object> getSignature() throws XbbException {
		String dir = ProBaseConfig.getProjectName();
		String host = "https://" + proBaseConfig.getBucketName() + "." + proBaseConfig.getEndpointSimple();
		OSSClient client = new OSSClient(proBaseConfig.getEndpointSimple(), proBaseConfig.getOssAccessKeyId(), proBaseConfig.getAccessKeySecret());
		try {
			long expireTime = SIGN_EXPIRE_TIME;
			long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
			Date expiration = new Date(expireEndTime);
			PolicyConditions policyConds = new PolicyConditions();
			policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
			policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

			String postPolicy = client.generatePostPolicy(expiration, policyConds);
			byte[] binaryData = postPolicy.getBytes("utf-8");
			String encodedPolicy = BinaryUtil.toBase64String(binaryData);
			String postSignature = client.calculatePostSignature(postPolicy);

			Map<String, Object> resultMap = new LinkedHashMap<>();
			resultMap.put("accessid", proBaseConfig.getOssAccessKeyId());
			resultMap.put("policy", encodedPolicy);
			resultMap.put("signature", postSignature);
			resultMap.put("dir", dir);
			resultMap.put("host", host);
			resultMap.put("expire", String.valueOf(expireEndTime / 1000));
			resultMap.put("viewurl", proBaseConfig.getViewurl());

			return resultMap;

		} catch (Exception e) {
			LOG.error("获取OSS签名错误", e);
			throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001, SystemErrorCodeEnum.API_ERROR_100001.getMsg());
		} finally {
			client.shutdown();
		}
	}

	public Map<String, Object> lowCodeSignature() throws XbbException{
		String dir = ProBaseConfig.getProjectName();
		String host = "https://" + proBaseConfig.getLowCodeBucketName() + "." + proBaseConfig.getLowCodeEndpointSimple();
		OSSClient client = new OSSClient(proBaseConfig.getLowCodeEndpointSimple(), proBaseConfig.getLowCodeAccessKeyId(), proBaseConfig.getLowCodeAccessKeySecret());
		try {
			long expireTime = SIGN_EXPIRE_TIME;
			long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
			Date expiration = new Date(expireEndTime);
			PolicyConditions policyConds = new PolicyConditions();
			policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
			policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

			String postPolicy = client.generatePostPolicy(expiration, policyConds);
			byte[] binaryData = postPolicy.getBytes("utf-8");
			String encodedPolicy = BinaryUtil.toBase64String(binaryData);
			String postSignature = client.calculatePostSignature(postPolicy);

			Map<String, Object> resultMap = new LinkedHashMap<>();
			resultMap.put("accessid", proBaseConfig.getLowCodeAccessKeyId());
			resultMap.put("policy", encodedPolicy);
			resultMap.put("signature", postSignature);
			resultMap.put("dir", dir);
			resultMap.put("host", host);
			resultMap.put("expire", String.valueOf(expireEndTime / 1000));
			resultMap.put("viewurl", proBaseConfig.getViewurl());

			return resultMap;

		} catch (Exception e) {
			LOG.error("获取OSS签名错误", e);
			throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001, SystemErrorCodeEnum.API_ERROR_100001.getMsg());
		} finally {
			client.shutdown();
		}
	}

	/**
	 * 文件断点续传上传获取凭证
	 * @param baseDTO
	 * @return
	 * @throws XbbException
	 */
	public Map<String, Object> getStsSignature(BaseDTO baseDTO) throws XbbException {
		String corpid = baseDTO.getCorpid();
		String userId = baseDTO.getUserId();
		int uploadLimitSize = 50;
		try {
			Client client = createClient(proBaseConfig.getOssAccessKeyId(),proBaseConfig.getAccessKeySecret());
			//RoleSessionName记录的是访问者的信息，长度为2-64位，超出64位会报错
			String policy = "{\n" +
					"     \"Version\": \"1\",\n" +
					"     \"Statement\": [\n" +
					"       {\n" +
					"         \"Effect\": \"Allow\",\n" +
					"         \"Action\": [\n" +
					"           \"oss:PutObject\",\n" +
					"           \"oss:GetObject\"\n" +
					"         ],\n" +
//					"         \"Action\": \"oss:PutObject\",\n" +
					"         \"Resource\": [\n" +
					"           \"acs:oss:*:*:%s\",\n" +
					"           \"acs:oss:*:*:%s/*\"\n" +
					"         ]\n" +
					"       }\n" +
					"     ]\n" +
					"   }";
			//临时凭证的权限是这里policy和阿里云oss设置的权限策略的交集，某个用户获取的临时凭证只拥有上传到 域名/projectName/corpid/userId/ 该目录文件夹下的权限，避免了临时凭证泄露的风险
			String dirLimit = proBaseConfig.getBucketName() + "/" + ProBaseConfig.getProjectName() + "/" + corpid + "/" + userId;
			AssumeRoleRequest assumeRoleRequest = new AssumeRoleRequest().setRoleArn(proBaseConfig.getRoleArn()).setRoleSessionName("_" + corpid).setPolicy(String.format(policy, dirLimit, dirLimit));
			AssumeRoleResponse assumeRoleResponse = client.assumeRole(assumeRoleRequest);
			//查询公司上传文件大小限制，没有配置上传文件默认50M(前端)
			CompanyConfigEntity companyConfigEntity = companyConfigModel.getByConfigAlias(CompanyConfigEnum.UPLOAD_LIMIT_SIZE.getAlias(), corpid);
			if (Objects.nonNull(companyConfigEntity) && StringUtil.isNotEmpty(companyConfigEntity.getConfigValue())) {
				String configValue = companyConfigEntity.getConfigValue();
				uploadLimitSize = Integer.parseInt(configValue);
			} else if (Objects.isNull(companyConfigEntity) && Objects.equals(baseDTO.getPlatform(), PlatFormEnum.DINGTALK.getValue()) && Objects.equals(corpid, "dinge3fa697f86d461d2")) {
				uploadLimitSize = 200;
			}
			return getResultMap(assumeRoleResponse, uploadLimitSize);
		} catch (Exception e) {
			LOG.error("获取OSS sts临时凭证错误", e);
			throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001, SystemErrorCodeEnum.API_ERROR_100001.getMsg());
		}

	}


	/**
	 * 文件断点续传上传获取凭证
	 * @param baseDTO
	 * @return
	 * @throws XbbException
	 */
	public Map<String, Object> getCopySignature(BaseDTO baseDTO) throws XbbException {
		String corpid = baseDTO.getCorpid();
		String userId = baseDTO.getUserId();
		int uploadLimitSize = 50;
		try {
			Client client = createClient(proBaseConfig.getOssAccessKeyId(),proBaseConfig.getAccessKeySecret());
			//RoleSessionName记录的是访问者的信息，长度为2-64位，超出64位会报错
			String policy = "{\n" +
					"     \"Version\": \"1\",\n" +
					"     \"Statement\": [\n" +
					"       {\n" +
					"         \"Effect\": \"Allow\",\n" +
					"         \"Action\": [\n" +
					"           \"oss:PutObject\",\n" +
					"           \"oss:GetObject\"\n" +
					"         ],\n" +
//					"         \"Action\": \"oss:PutObject\",\n" +
					"         \"Resource\": [\n" +
					"           \"acs:oss:*:*:%s\",\n" +
					"           \"acs:oss:*:*:%s/*\"\n" +
					"         ]\n" +
					"       }\n" +
					"     ]\n" +
					"   }";
			//临时凭证的权限是这里policy和阿里云oss设置的权限策略的交集，某个用户获取的临时凭证只拥有上传到 域名/projectName/corpid 该目录文件夹下的权限，避免了临时凭证泄露的风险
			String dirLimit = proBaseConfig.getBucketName() + "/" + ProBaseConfig.getProjectName() + "/" + corpid ;
			AssumeRoleRequest assumeRoleRequest = new AssumeRoleRequest().setRoleArn(proBaseConfig.getRoleArn()).setRoleSessionName("_" + corpid).setPolicy(String.format(policy, dirLimit, dirLimit));
			AssumeRoleResponse assumeRoleResponse = client.assumeRole(assumeRoleRequest);
			//查询公司上传文件大小限制，没有配置上传文件默认50M(前端)
			CompanyConfigEntity companyConfigEntity = companyConfigModel.getByConfigAlias(CompanyConfigEnum.UPLOAD_LIMIT_SIZE.getAlias(), corpid);
			if (Objects.nonNull(companyConfigEntity) && StringUtil.isNotEmpty(companyConfigEntity.getConfigValue())) {
				String configValue = companyConfigEntity.getConfigValue();
				uploadLimitSize = Integer.parseInt(configValue);
			} else if (Objects.isNull(companyConfigEntity) && Objects.equals(baseDTO.getPlatform(), PlatFormEnum.DINGTALK.getValue()) && Objects.equals(corpid, "dinge3fa697f86d461d2")) {
				uploadLimitSize = 200;
			}
			return getResultMap(assumeRoleResponse, uploadLimitSize);
		} catch (Exception e) {
			LOG.error("获取OSS sts临时凭证错误", e);
			throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001, SystemErrorCodeEnum.API_ERROR_100001.getMsg());
		}

	}

	/**
	 * 外链文件断点续传上传获取凭证
	 * @param baseDTO
	 * @return
	 * @throws XbbException
	 */
	 public Map<String, Object> getOuterLinkStsSignature(BaseDTO baseDTO) throws XbbException {
		 try {
			 Client client = createClient(proBaseConfig.getOssAccessKeyId(),proBaseConfig.getAccessKeySecret());
			 //RoleSessionName记录的是访问者的信息，长度为2-64位，超出64位会报错
			 //外链上传文件会话信息写死
			 AssumeRoleRequest assumeRoleRequest = new AssumeRoleRequest().setRoleArn(proBaseConfig.getRoleArn()).setRoleSessionName("fromOuterLink");
			 AssumeRoleResponse assumeRoleResponse = client.assumeRole(assumeRoleRequest);
			 //外链上传文件大小写死为50M
			 int uploadLimitSize = Objects.equals(baseDTO.getPlatform(), PlatFormEnum.DINGTALK.getValue()) ? 20 : 50;
			 return getResultMap(assumeRoleResponse, uploadLimitSize);
		 } catch (Exception e) {
			 LOG.error("获取OSS 外链sts临时凭证错误", e);
			 throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001, SystemErrorCodeEnum.API_ERROR_100001.getMsg());
		 }
	}

	/**
	 * 封装sts文件上传回参
	 * @param assumeRoleResponse
	 * @param uploadLimitSize
	 * @return
	 * @throws XbbException
	 */
	private Map<String, Object> getResultMap(AssumeRoleResponse assumeRoleResponse,  int uploadLimitSize) throws XbbException {
		Map<String, Object> resultMap = new HashMap<>(PaasConstant.DEFAULT_ACCURACY);
		//临时凭证
		AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials credentials = assumeRoleResponse.getBody().getCredentials();
        //accessKeyId->encryption
        resultMap.put("encryption", credentials.getAccessKeyId());
        //accessKeySecret->signatureToken
        resultMap.put("signatureToken", credentials.getAccessKeySecret());
		resultMap.put("securityToken", credentials.getSecurityToken());
		resultMap.put("bucket", proBaseConfig.getBucketName());
		resultMap.put("region", proBaseConfig.getRegion());
		resultMap.put("dir", ProBaseConfig.getProjectName());
		resultMap.put("viewurl", proBaseConfig.getViewurl());
		resultMap.put("uploadLimitSize", uploadLimitSize);
		return resultMap;
	}

	private  Client createClient(String accessKeyId, String accessKeySecret) throws Exception {
		Config config = new Config()
				// AccessKey ID
				.setAccessKeyId(accessKeyId)
				// AccessKey Secret
				.setAccessKeySecret(accessKeySecret);
		// 访问的sts域名
		config.endpoint = proBaseConfig.getStsEndPoint();
		return new com.aliyun.sts20150401.Client(config);
	}

	/**
	 * 拷贝oss文件
	 * @param bucketName 源Bucket名称
	 * @param sourceKey 源Object的完整路径，完整路径中不能包含Bucket名称
	 * @param destinationBucketName 写与源Bucket处于同一地域的目标Bucket名称
	 * @param destinationKey 目标Object的完整路径。Object完整路径中不能包含Bucket名称
	 * @throws XbbException exception
	 * @author 李少杰
	 */
	public void copyObject(String bucketName, String sourceKey, String destinationBucketName, String destinationKey) throws XbbException {
		OSSClient client = new OSSClient(proBaseConfig.getLowCodeEndpointSimple(), proBaseConfig.getLowCodeAccessKeyId(), proBaseConfig.getLowCodeAccessKeySecret());
		try {
			// 拷贝文件
			CopyObjectResult result = client.copyObject(bucketName, sourceKey, destinationBucketName, destinationKey);
			LOG.info("OSS拷贝文件成功：" + "ETag: " + result.getETag() + " LastModified: " + result.getLastModified());
		} catch (Exception e) {
			LOG.error("获取OSS签名错误", e);
			throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001, e.getMessage());
		} finally {
			client.shutdown();
		}
	}

	/**
	 * 拷贝低代码文件
	 * @param sourceKey 源文件
	 * @param destinationKey 目标文件
	 * @throws XbbException exception
	 * @author 李少杰
	 */
	public void copyLowCodeFile(String sourceKey, String destinationKey) throws XbbException {
		String lowCodeBucketName = proBaseConfig.getLowCodeBucketName();
		copyObject(lowCodeBucketName, sourceKey, lowCodeBucketName, destinationKey);
	}
}
